import { Types, defineComponent, addComponent, removeComponent, hasComponent, defineQuery } from '$lib/ecs';
import { isLandscape } from './landscape';

/**
 * Component for coordinates. It's not directly exposed to other modules to avoid
 * setting coords manually without updating grid cache
 */
const Pos2 = defineComponent('Pos2', {
  x: Types.i32,
  y: Types.i32,
});

export const coordQuery = defineQuery([Pos2]);
export const coordPlusQuery = (others) => defineQuery([...others, Pos2]);

export const hasPos2 = (world, e) => {
  return hasComponent(world, Pos2, e);
}

export const getPos2 = (e) => {
  return {
    x: Pos2.x[e],
    y: Pos2.y[e],
  };
};

export const distance = (a, b) => {
  return Math.max(Math.abs(a.x-b.x), Math.abs(a.y-b.y));
};

const grid = new Map();

/**
 * Initially load map cache (to do after all components loaded from a save)
 */
export const loadWorldOnMap = (world) => {
  const es = coordQuery(world);
  // TODO: optimize size detection
  for (let e of es) {
    let {x, y} = getPos2(e);
    registerOnGrid(world, e, x, y);
  }
};

export const placeOnMap = (world, e, x, y) => {
  addComponent(world, Pos2, e);
  moveTo(world, e, x, y);
};

export const removeFromMap = (world, e) => {
  const {x, y} = getPos2(e);
  removeFrom(world, e, x, y);
  removeComponent(world, Pos2, e);
  /// TODO
};

export const move = (world, e, x, y) => {
  removeFrom(world, e, Pos2.x[e], Pos2.y[e]);
  moveTo(world, e, x, y);
};

export const removeFrom = (world, e, x, y) => {
  const stack = getStack(x, y);
  const ix = stack.indexOf(e);
  if (ix === -1) {
    console.warn('Trying to remove entity from grid cell where it does not belong');
    return;
  }
  stack.splice(ix, 1);
};

let locationTriggers = [];

export const registerLocationTrigger = (component, trigger) => {
  locationTriggers.push({component, trigger});
};

const processFeatureTriggers = (world, e, x, y) => {
  const stack = getStack(x, y);
  for (let other of stack) {
    if (e === other)
      continue;
    for (let {component, trigger} of locationTriggers) {
      if (hasComponent(world, component, other)) {
        trigger(world, other, e);
      }
    }
  }
};

export const moveTo = (world, e, x, y) => {
  Pos2.x[e] = x;
  Pos2.y[e] = y;
  registerOnGrid(world, e, x, y);
  processFeatureTriggers(world, e, x, y);
};

const registerOnGrid = (world, e, x, y) => {
  getStack(x, y).push(e);
  // Make modular?
  if (isLandscape(world, e)) {
    registerLandscape(world, e);
  }
};

export const registerLandscape = (world, e) => {
  let {x, y} = getPos2(e);
  getTile(x, y).landscape = e;
};

const getTile = (x, y) => {
  let line = grid.get(y);
  if (line === undefined) {
    line = new Map();
    grid.set(y, line);
  }
  let tile = line.get(x);
  if (tile === undefined) {
    tile = {
      stack: [],
      landscape: undefined,
    };
    line.set(x, tile);
  }
  return line.get(x);
};

const getStack = (x, y) => {
  return getTile(x, y).stack;
};

export const onTile = (world, x, y) => {
  return getStack(x, y);
};

export const landscapeAt = (world, x, y) => {
  return getTile(x, y).landscape;
};

/**
 * Get map piece around entitry max dist of r
 */
export const getGridAt = (world, e, r) => {
  const {x, y} = getPos2(e);
  const res = new Map();
  for (let dy = -r; dy <= r; ++dy) {
    const line = new Map();
    res.set(dy, line);
    for (let dx = -r; dx <= r; ++dx) {
      line.set(dx, {
        stack: getStack(x+dx, y+dy)
      });
    }
  }
  return res;
};

/**
 * map over grid area
 */
export const gridMapOn = (grid, x0, y0, x1, y1, f) => {
  const res = new Map();
  for (let y = y0; y <= y1; ++y) {
    const line = new Map();
    res.set(y, line);
    for (let x = x0; x <= x1; ++x) {
      line.set(x, f(grid.get(y).get(x)));
    }
  }
  return res;
};
